# -*- coding:utf-8 -*-
import os
from xml.dom.minidom import parse
import xml.dom.minidom
import pymysql


def get_file_extension(path):
    return os.path.splitext(path)[1]


def define_VM(conn, conf_file):
    f = open(conf_file)
    xml = f.read()
    conn.defineXML(xml)


def get_vm_name_from_xml(raw_xml):
    dom_tree = xml.dom.minidom.parse(raw_xml)
    domain = dom_tree.documentElement
    vm_name = domain.getElementsByTagName('name')[0]
    return vm_name.childNodes[0].data


def get_vm_memory_from_xml(raw_xml):
    dom_tree = xml.dom.minidom.parse(raw_xml)
    domain = dom_tree.documentElement
    vm_memory = domain.getElementsByTagName('memory')[0]
    return vm_memory.childNodes[0].data


def get_vm_cpu_from_xml(raw_xml):
    dom_tree = xml.dom.minidom.parse(raw_xml)
    domain = dom_tree.documentElement
    vm_vcpu = domain.getElementsByTagName('vcpu')[0]
    return vm_vcpu.childNodes[0].data


def add_defined_name_and_path(name, image_path):
    db_conn = pymysql.connect(host='localhost',
                              port=3306,
                              user='root',
                              passwd='1234qwer',
                              db='libvirt')
    cur = db_conn.cursor()
    sql_query = "select * from defined_name_and_path_lists where name = '" + name + "'"
    sql_insert = "insert into defined_name_and_path_lists values('" + name + "','" + image_path + "')"
    result_query = cur.execute(sql_query)
    if result_query <= 0:  # 如果数据库中没有名叫name的数据，则插入数据
        cur.execute(sql_insert)
    cur.close()
    db_conn.commit()
    db_conn.close()


def add_vmware_defined(vm_name, path):
    rootdir = os.path.abspath(os.path.join(path, os.pardir))
    db_conn = pymysql.connect(host='localhost',
                              port=3306,
                              user='root',
                              passwd='1234qwer',
                              db='libvirt')
    cur = db_conn.cursor()
    sql_query = "select * from vmware_defined where name = '" + vm_name + "'"
    sql_insert = "insert into vmware_defined values('" + vm_name + "','" + rootdir + "',-1)"
    # runningState： -1代表关机
    result_query = cur.execute(sql_query)
    if result_query <= 0:  # 如果数据库中已经有名叫name的数据，则不在重复插入数据
        cur.execute(sql_insert)
    cur.close()
    db_conn.commit()
    db_conn.close()


def get_vm_image_path_from_xml(xml_path):
    xml_dom = xml.dom.minidom.parse(xml_path)
    domain_node = xml_dom.getElementsByTagName("domain")[0]
    devices = domain_node.getElementsByTagName("source")
    return devices[0].getAttribute("file")


def check_imagepath_used_or_not(image_path):
    db_conn = pymysql.connect(host='localhost',
                              port=3306,
                              user='root',
                              passwd='1234qwer',
                              db='libvirt')
    cur = db_conn.cursor()
    sql_query = "select * from defined_name_and_path_lists where path = '" + image_path + "'"
    result_query = cur.execute(sql_query)
    cur.close()
    db_conn.commit()
    db_conn.close()
    if result_query > 0:
        return True
    else:
        return False


def check_vmname_used_or_not(name):
    db_conn = pymysql.connect(host='localhost',
                              port=3306,
                              user='root',
                              passwd='1234qwer',
                              db='libvirt')
    cur = db_conn.cursor()
    sql_query = "select * from defined_name_and_path_lists where name = '" + name + "'"
    result_query = cur.execute(sql_query)
    cur.close()
    db_conn.commit()
    db_conn.close()
    if result_query > 0:
        return True
    else:
        return False


def ergodic_dir(conn, rootdir, is_vmware):
    for lists in os.listdir(rootdir):
        path = os.path.join(rootdir, lists)
        if get_file_extension(path) == '.xml':
            add_defined_name_and_path(get_vm_name_from_xml(path), get_vm_image_path_from_xml(path))
            if is_vmware:
                add_vmware_defined(get_vm_name_from_xml(path), get_vm_image_path_from_xml(path))
            if not check_defined_instance(conn, get_vm_name_from_xml(path)) \
                    and not check_started_instance(conn, get_vm_name_from_xml(path)):
                define_VM(conn, path)
        if os.path.isdir(path):
            ergodic_dir(conn, path, is_vmware)


def define_from_xml(conn, VM_path, is_vmware):
    try:
        path2 = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
        image_path = path2 + '/' + VM_path
        ergodic_dir(conn, image_path, is_vmware)

        # 初始化 用户已经定义的vmware虚拟机(因为vmware虚拟机，在上一次定义过之后，libvirt不会在下次自动带入)
        if is_vmware:
            db_conn = pymysql.connect(host='localhost',
                                      port=3306,
                                      user='root',
                                      passwd='1234qwer',
                                      db='libvirt')
            cur = db_conn.cursor()
            sql_query = "select * from vmware_defined"
            result_query = cur.execute(sql_query)
            info = cur.fetchmany(result_query)
            for ii in info:
                # print(str(ii))  # str(ii) 原始字符：('test', '/host/selfBuild/image/vmWare/test', -1)
                path = str(ii).split(',')[1].strip()
                path = path.replace("'", '')
                ergodic_dir(conn, path, is_vmware)
            cur.close()
            db_conn.commit()
            db_conn.close()
        """
        # 已启动的虚拟机，未被记录到name_and_path_lists，所以需要添加进去
        db_conn = pymysql.connect(host='localhost',
                                  port=3306,
                                  user='root',
                                  passwd='1234qwer',
                                  db='libvirt')
        cur = db_conn.cursor()

        names = show_started_list(conn)
        for name in names:
            sql_query = "select * from defined_name_and_path_lists where name = '" + name + "'"
            result_query = cur.execute(sql_query)
            if result_query <= 0:  # 如果数据库中没有名叫name的数据，则插入数据
                dom = conn.lookupByName(name)
                raw_xml = dom.XMLDesc(0)
                dom_tree = xml.dom.minidom.parseString(raw_xml)
                domain_sources = dom_tree.getElementsByTagName('source')
                for source in domain_sources:
                    if get_file_extension(source.getAttribute('file')) == '.vmdk' \
                            or get_file_extension(source.getAttribute('file')) == '.qcow2' \
                            or get_file_extension(source.getAttribute('file')) == '.raw':
                        add_defined_name_and_path(name, source.getAttribute('file'))
                        break
        cur.close()
        db_conn.commit()
        db_conn.close()
        # 已定义的虚拟机，未被添加到name_and_path_lists，所以需要添加进去
        db_conn = pymysql.connect(host='localhost',
                                  port=3306,
                                  user='root',
                                  passwd='1234qwer',
                                  db='libvirt')
        cur = db_conn.cursor()

        defined_names = show_defined_list(conn)
        for define_name in defined_names:
            sql_query = "select * from defined_name_and_path_lists where name = '" + define_name + "'"
            result_query = cur.execute(sql_query)
            if result_query <= 0:  # 如果数据库中没有名叫name的数据，则插入数据
                dom = conn.lookupByName(define_name)
                raw_xml = dom.XMLDesc(0)
                dom_tree = xml.dom.minidom.parseString(raw_xml)
                domain_sources = dom_tree.getElementsByTagName('source')
                for source in domain_sources:
                    if get_file_extension(source.getAttribute('file')) == '.vmdk' \
                            or get_file_extension(source.getAttribute('file')) == '.qcow2' \
                            or get_file_extension(source.getAttribute('file')) == '.raw':
                        add_defined_name_and_path(define_name, source.getAttribute('file'))
                        break
        cur.close()
        db_conn.commit()
        db_conn.close()
        """
    except:
        print ('hypervisor: define from xml error')
        raise Exception('define_from_user error')


def create_xml(xml_path, image_path, vm_name, cpu, memory):
    xml_dom = xml.dom.minidom.parse(xml_path)
    domain_node = xml_dom.getElementsByTagName("domain")[0]
    name_node = domain_node.getElementsByTagName("name")
    name_node[0].childNodes[0].data = vm_name
    cpu_node = domain_node.getElementsByTagName("vcpu")
    cpu_node[0].childNodes[0].data = cpu
    memory_node = domain_node.getElementsByTagName("memory")
    memory_node[0].childNodes[0].data = memory
    devices = domain_node.getElementsByTagName("source")
    devices[0].setAttribute("file", image_path)
    writeFile = open(xml_path, 'w')
    xml_bytes = xml_dom.toxml('utf-8')
    xml_str = str(xml_bytes)
    xml_str_delete = xml_str[2:-1]
    writeFile.write(xml_str_delete)


def define_from_user(conn, config_path, image_path, vm_name, cpu, memory):
    try:
        create_xml(config_path, image_path, vm_name, cpu, memory)
        add_defined_name_and_path(vm_name, image_path)
        define_VM(conn, config_path)
        # 将用户自定义的vmware虚拟机name及镜像path插入数据库，便于下次初始化时使用
        if os.path.splitext(image_path)[1] == '.vmdk':
            path = os.path.abspath(os.path.join(config_path, os.pardir))
            db_conn = pymysql.connect(host='localhost',
                                      port=3306,
                                      user='root',
                                      passwd='1234qwer',
                                      db='libvirt')
            cur = db_conn.cursor()
            sql_query = "select * from vmware_defined where name = '" + vm_name + "'"
            sql_insert = "insert into vmware_defined values('" + vm_name + "','" + path + "',-1)"
            # runningState： -1代表关机
            result_query = cur.execute(sql_query)
            if result_query <= 0:  # 如果数据库中已经有名叫name的数据，则不在重复插入数据
                cur.execute(sql_insert)
            cur.close()
            db_conn.commit()
            db_conn.close()
    except:
        raise Exception('define_from_user error')


def start_vm(conn, instance, is_vmware):
    try:
        dom = conn.lookupByName(instance)
        dom.create()
        """
        if is_vmware:
            db_conn = pymysql.connect(host='localhost',
                                      port=3306,
                                      user='root',
                                      passwd='1234qwer',
                                      db='libvirt')
            cur = db_conn.cursor()
            sql_update = "update vmware_defined set runningState=1 where name = '" + instance + "'"
            # runningState： 1代表运行
            cur.execute(sql_update)
            cur.close()
            db_conn.commit()
            db_conn.close()
        """
    except:
        print ('hypervisor: start vm error')
        raise Exception('hypervisor: start vm error')


def get_kvm_vnc_port(conn, instance):
    dom = conn.lookupByName(instance)
    if check_started_instance(conn, instance):
        raw_xml = dom.XMLDesc(0)
        dom_tree = xml.dom.minidom.parseString(raw_xml)
        domain_graphics = dom_tree.getElementsByTagName('graphics')
        if len(domain_graphics) > 0:
            return domain_graphics[0].getAttribute('port')
        else:
            return -1


def get_all_kvm_started_vnc_ports(conn):
    names = show_started_list(conn)
    port_list = []
    for name in names:
        port = get_kvm_vnc_port(conn, name)
        port_list.append(port)
    return port_list


def get_port_from_vmx(vmx_path):
    file_des = open(vmx_path)
    line = file_des.readline()
    while line:
        line = file_des.readline()
        if line.__contains__('RemoteDisplay.vnc.port'):
            port = line.split('=')[1].replace('"', ' ').strip()
            file_des.close()
            return port


def get_vmware_vnc_port(conn, name):
    port = ''
    if check_started_instance(conn, name):
        db_conn = pymysql.connect(host='localhost',
                                  port=3306,
                                  user='root',
                                  passwd='1234qwer',
                                  db='libvirt')
        cur = db_conn.cursor()
        sql_query = "select path from defined_name_and_path_lists where name='" + name + "'"
        result_query = cur.execute(sql_query)
        if result_query > 0:  # 如果存在，则处理查询结果
            info = cur.fetchmany(result_query)  # 从数据库查询出来的原始形式，类似： (('/host/selfBuild/image/vmWare/test/win10.vmdk',),)
            cur.close()
            db_conn.commit()
            db_conn.close()
            image_path = str(info).split("'")[1]  # 经过处理后的路径，类似： /host/selfBuild/image/vmWare/test/win10.vmdk
            rootdir = os.path.abspath(os.path.join(image_path, os.pardir))
            return get_port_from_vmx(rootdir + '/' + name + '.vmx')
        else:
            cur.close()
            db_conn.commit()
            db_conn.close()

        dom = conn.lookupByName(name)
        raw_xml = dom.XMLDesc(0)
        dom_tree = xml.dom.minidom.parseString(raw_xml)
        domain_graphics = dom_tree.getElementsByTagName('graphics')
        if len(domain_graphics) > 0:
            return domain_graphics[0].getAttribute('port')
        else:
            return -1


def get_all_vmware_started_vnc_ports(conn):
    names = show_started_list(conn)
    port_list = []
    for name in names:
        port_list.append(get_vmware_vnc_port(conn, name))
    return port_list


def shutdown_and_destroy_vm(conn, instance, is_vmware):
    try:
        dom = conn.lookupByName(instance)
        if is_vmware:
            dom.shutdown()
            """
            db_conn = pymysql.connect(host='localhost',
                                      port=3306,
                                      user='root',
                                      passwd='1234qwer',
                                      db='libvirt')
            cur = db_conn.cursor()
            sql_update = "update vmware_defined set runningState=-1 where name = '" + instance + "'"
            # runningState： -1代表关机
            cur.execute(sql_update)
            cur.close()
            db_conn.commit()
            db_conn.close()
            """
        else:
            dom.destroy()
    except:
        print ('hypervisor: shutdown and destroy vm error')
        raise Exception('hypervisor: shutdown and destroy vm error')


def pause_VM(conn, instance, is_vmware):
    try:
        dom = conn.lookupByName(instance)
        dom.suspend()
        """
        if is_vmware:
            db_conn = pymysql.connect(host='localhost',
                                      port=3306,
                                      user='root',
                                      passwd='1234qwer',
                                      db='libvirt')
            cur = db_conn.cursor()
            sql_update = "update vmware_defined set runningState=3 where name = '" + instance + "'"
            # runningState： 3代表暂停
            cur.execute(sql_update)
            cur.close()
            db_conn.commit()
            db_conn.close()
        """
    except:
        print ('hypervisor: suspend vm error')
        raise Exception('hypervisor: suspend vm error')


def resume_VM(conn, instance, is_vmware):
    try:
        dom = conn.lookupByName(instance)
        dom.resume()
        """
        if is_vmware:
            db_conn = pymysql.connect(host='localhost',
                                      port=3306,
                                      user='root',
                                      passwd='1234qwer',
                                      db='libvirt')
            cur = db_conn.cursor()
            sql_update = "update vmware_defined set runningState=1 where name = '" + instance + "'"
            # runningState： 1代表运行
            cur.execute(sql_update)
            cur.close()
            db_conn.commit()
            db_conn.close()
        """

    except:
        print ('hypervisor: resume vm error')
        raise Exception('hypervisor: resume vm error')


def undefine_vm(conn, instance, delete_image_or_not):
    if delete_image_or_not.lower() == 'false':  # 不删除镜像的情况，name和path在此处从defined_name_and_path_lists中删除
        # 从数据库 defined_name_and_path_lists 中删除 undefine 的数据
        db_conn = pymysql.connect(host='localhost',
                                  port=3306,
                                  user='root',
                                  passwd='1234qwer',
                                  db='libvirt')
        cur = db_conn.cursor()
        sql_delete = "delete from defined_name_and_path_lists where name = '" + instance + "'"
        cur.execute(sql_delete)
        cur.close()
        db_conn.commit()
        db_conn.close()

    try:
        dom = conn.lookupByName(instance)
        dom.undefine()
        # 从数据库中删除 undefine 的数据
        db_conn = pymysql.connect(host='localhost',
                                  port=3306,
                                  user='root',
                                  passwd='1234qwer',
                                  db='libvirt')
        cur = db_conn.cursor()
        sql_delete = "delete from vmware_defined where name = '" + instance + "'"
        cur.execute(sql_delete)
        cur.close()
        db_conn.commit()
        db_conn.close()
    except:
        print ('hypervisor: undefine vm error')
        raise Exception('hypervisor: undefine vm error')


def delete_image_exec(instance, delete_image_or_not):
    db_conn = pymysql.connect(host='localhost',
                              port=3306,
                              user='root',
                              passwd='1234qwer',
                              db='libvirt')
    cur = db_conn.cursor()
    # 根据名称从数据库中获取路径
    sql_query = "select path from defined_name_and_path_lists where name='" + instance + "'"
    result_query = cur.execute(sql_query)
    if result_query > 0:  # 如果存在，则处理数据
        info = cur.fetchmany(result_query)  # 从数据库查询出来的原始形式，类似： (('/host/selfBuild/image/vmWare/test'/win10.vmdk,),)
        image_path = str(info).split("'")[1]  # 经过处理后的路径，类似： /host/selfBuild/image/vmWare/test/win10.vmdk
        rootdir = os.path.abspath(os.path.join(image_path, os.pardir))
        os.popen('rm -rf ' + rootdir)
    # 从数据库中 删除对应名称的数据
    sql_delete = "delete from defined_name_and_path_lists where name = '" + instance + "'"
    cur.execute(sql_delete)
    cur.close()
    db_conn.commit()
    db_conn.close()


def show_VM_detail_info(conn, instance, is_vmware):
    domain = conn.lookupByName(instance)
    infos = domain.info()
    """
    if is_vmware:
        db_conn = pymysql.connect(host='localhost',
                                  port=3306,
                                  user='root',
                                  passwd='1234qwer',
                                  db='libvirt')
        cur = db_conn.cursor()
        sql_query = "select runningState from vmware_defined where name = '" + instance + "'"
        result_query = cur.execute(sql_query)
        info_orig = cur.fetchmany(result_query)
        info = str(info_orig).split(',')[0].replace('(', '')
        info_int = int(info)
        cur.close()
        db_conn.commit()
        db_conn.close()
        detail_info = VMDetailInfo(id, domain.name(), info_int, infos[1], infos[2], infos[3], (infos[4] / (1000 * 1000)))
    else:
    """
    detail_info = VMDetailInfo(id, domain.name(), infos[0], infos[1], infos[2], infos[3], (infos[4] / (1000 * 1000)))
    return detail_info


def get_shutdown_vm_detail_info(vm_name):
    path = ''
    db_conn = pymysql.connect(host='localhost',
                              port=3306,
                              user='root',
                              passwd='1234qwer',
                              db='libvirt')
    cur = db_conn.cursor()
    # 根据名称从数据库中获取路径
    sql_query = "select path from defined_name_and_path_lists where name='" + vm_name + "'"
    result_query = cur.execute(sql_query)
    if result_query > 0:   # 如果存在，则处理数据
        info = cur.fetchmany(result_query)  # 从数据库查询出来的原始形式，类似： (('/host/selfBuild/image/vmWare/test/win10.vmdk',),)
        path = str(info).split("'")[1]  # 经过处理后的路径，类似： /host/selfBuild/image/vmWare/test/win10.vmdk
    cur.close()
    db_conn.commit()
    db_conn.close()

    rootdir = os.path.abspath(os.path.join(path, os.pardir))
    for lists in os.listdir(rootdir):
        path = os.path.join(rootdir, lists)
        if get_file_extension(path) == '.xml':
            shutdown_vm_detail_info = ShutdownVMDetailInfo(get_vm_cpu_from_xml(path), get_vm_memory_from_xml(path))
            return shutdown_vm_detail_info


def get_defined_name_and_path_lists():
    defined_name_and_path_lists = []
    db_conn = pymysql.connect(host='localhost',
                              port=3306,
                              user='root',
                              passwd='1234qwer',
                              db='libvirt')
    cur = db_conn.cursor()
    sql_query = "select * from defined_name_and_path_lists"
    result_query = cur.execute(sql_query)
    info = cur.fetchmany(result_query)
    for ii in info:
        name_orig = str(ii).split()[0].replace('(', '')
        name = name_orig.replace("'", "").replace(",", "")
        path_orig = str(ii).split()[1].replace(')', '')
        path = path_orig.replace("'", "")
        rootdir = os.path.abspath(os.path.join(path, os.pardir))

        name_and_path = NameAndPath(name, rootdir)
        defined_name_and_path_lists.append(name_and_path)
    cur.close()
    db_conn.commit()
    db_conn.close()
    return defined_name_and_path_lists


def get_defined_path(vm_name):
    rootdir = ''
    db_conn = pymysql.connect(host='localhost',
                              port=3306,
                              user='root',
                              passwd='1234qwer',
                              db='libvirt')
    cur = db_conn.cursor()
    sql_query = "select path from defined_name_and_path_lists where name='" + vm_name + "'"
    result_query = cur.execute(sql_query)
    if result_query > 0:  # 如果存在，则处理数据
        info = cur.fetchmany(result_query)  # 从数据库查询出来的原始形式，类似： (('/host/selfBuild/image/vmWare/test/win10.vmdk',),)
        path = str(info).split("'")[1]
        rootdir = os.path.abspath(os.path.join(path, os.pardir))
    return rootdir


def show_defined_list(conn):
    names = conn.listDefinedDomains()
    return names


def show_started_list(conn):
    IDs = conn.listDomainsID()
    names = []
    for dom_id in IDs:
        dom = conn.lookupByID(dom_id)
        name = dom.name()
        names.append(name)
    return names


def check_defined_instance(conn, instance):
    names = conn.listDefinedDomains()
    return instance in names


def check_started_instance(conn, instance):
    IDs = conn.listDomainsID()
    for dom_id in IDs:
            dom = conn.lookupByID(dom_id)
            name = dom.name()
            if instance == name:
                return True
    return False


class Hypervisor(object):

    def __init__(self):
        self.conn = None
        self.VM_path = None

    def init_define(self, is_vmware):
        define_from_xml(self.conn, self.VM_path, is_vmware)

    def define(self, config_path, image_path, vm_name, cpu, memory):
        define_from_user(self.conn, config_path, image_path, vm_name, cpu, memory)

    def undefine(self, name, delete_image_or_not):
        undefine_vm(self.conn, name, delete_image_or_not)

    @staticmethod
    def delete_image(name, delete_image_or_not):
        delete_image_exec(name, delete_image_or_not)

    def start(self, name, is_vmware):
        start_vm(self.conn, name, is_vmware)

    def shutdown(self, name, is_vmware):
        shutdown_and_destroy_vm(self.conn, name, is_vmware)

    def pause(self, name, is_vmware):
        pause_VM(self.conn, name, is_vmware)

    def resume(self, name, is_vmware):
        resume_VM(self.conn, name, is_vmware)

    def list_defined(self):
        return show_defined_list(self.conn)

    def list_started(self):
        return show_started_list(self.conn)

    def get_kvm_vnc_port(self, name):
        return get_kvm_vnc_port(self.conn, name)

    def get_vmware_vnc_port(self, name):
        return get_vmware_vnc_port(self.conn, name)

    def get_all_kvm_started_ports_list(self):
        return get_all_kvm_started_vnc_ports(self.conn)

    def get_all_vmware_started_ports_list(self):
        return get_all_vmware_started_vnc_ports(self.conn)

    def check_defined_instance(self, name):
        return check_defined_instance(self.conn, name)

    def check_started_list(self, name):
        return check_started_instance(self.conn, name)

    @staticmethod
    def check_imagepath_used_or_not(image_path):
        return check_imagepath_used_or_not(image_path)

    @staticmethod
    def check_vmname_used_or_not(name):
        return check_vmname_used_or_not(name)

    def list_detail_info(self):
        return show_VM_detail_info(self.conn)

    def get_detail_info(self, name, is_vmware):
        return show_VM_detail_info(self.conn, name, is_vmware)

    @staticmethod
    def get_shutdown_vm_detail_info(vm_name):
        return get_shutdown_vm_detail_info(vm_name)

    @staticmethod
    def get_defined_name_and_path_lists():
        return get_defined_name_and_path_lists()

    @staticmethod
    def get_defined_path(vm_name):
        return get_defined_path(vm_name)

    def close_conn(self):
        self.conn.close()


class VMDetailInfo(object):
    def __init__(self, id, name, running_state, max_memory, memory, number_CPU, CPU_time):
        self.id = id                        # int
        self.name = name                    # str
        self.running_state = running_state  # int
        self.max_memory = max_memory        # long
        self.memory = memory                # long
        self.number_CPU = number_CPU        # int
        self.CPU_time = CPU_time            # long


class ShutdownVMDetailInfo(object):
    def __init__(self, cpu, memory):
        self.cpu = cpu        # int
        self.memory = memory  # long


class NameAndPath(object):
    def __init__(self, name, path):
        self.name = name
        self.path = path